package mt.comm.implement;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.LinkedList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import mt.comm.implement.ServerSideMessage.Type;

/**
 * The Class ClientHandler.
 */

public class ClientHandler {

	/**
	 * List of messages from client to server
	 */
	private BlockingQueue<ClientSideMessageImp> messagesFromServerToClient = new LinkedBlockingQueue<ClientSideMessageImp>();
	private LinkedList<Thread> activeThreads = new LinkedList<Thread>();
	/**
	 * Socket
	 */
	private Socket socket;

	/** The in. -> Messages incoming from the client */
	private ObjectInputStream in;

	/** The out.->Messages outgoing from the server */
	private ObjectOutputStream out;

	/** The server communication. */
	private ServerCommImp serverComm;
	private ClientHandler handler = this;
	private String nickname;

	/**
	 * Instantiates a new client handler, InputStream and OutputStream and adds
	 * the threads created to handle the messages to an active threads list
	 * 
	 * 
	 * @param socket
	 *            the socket
	 * @param serverComm
	 *            the server comm
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */

	public ClientHandler(Socket socket, ServerCommImp serverComm)
			throws IOException {
		this.socket = socket;
		this.serverComm = serverComm;
		out = new ObjectOutputStream(socket.getOutputStream());
		in = new ObjectInputStream(socket.getInputStream());
		// Add the threads to active
		activeThreads.add(new ThreadSendToClient());
		activeThreads.add(new ThreadIncomingFromClient());
	}

	/**
	 * Gets the messages from the inputStream and handles them Checks if it's a
	 * users first connect and connects him, if not sends an exception message
	 * Checks if it's a disconnect message, if it is handles the termination
	 * process
	 * 
	 */
	private class ThreadIncomingFromClient extends Thread {
		@Override
		public void run() {
			try {
				while (!isInterrupted()) {
					ServerSideMessageImp messageIn = (ServerSideMessageImp) in
							.readObject();
					synchronized (serverComm) {
						Type type = messageIn.getType();
						if (type == Type.CONNECTED) {
							if (!getServerComm().getConnectedClients()
									.containsKey(messageIn.getSenderNickname())) {
								setNickname(messageIn.getSenderNickname());
								getServerComm().getConnectedClients().put(
										getNickname(), getHandler());
								getServerComm().getMessagesFromClientToServer()
										.add(messageIn);

							} else {
								getMessagesFromServerToClient()
										.add(new ClientSideMessageImp(
												ClientSideMessageImp.Type.ERROR,
												null,
												"ERROR:This nickname is already connected"));
							}
						} else if (type == Type.DISCONNECTED) {
							System.out.println("Disconnected client");
							// getServerComm().getMessagesFromClientToServer().add(messageIn);
							setNickname(messageIn.getSenderNickname());
							serverComm.disconnectClient(nickname);
							disconnectClient();

						} else
							getServerComm().getMessagesFromClientToServer()
									.add(messageIn);

					}

				}
			} catch (Exception e) {
				// System.out.println("Cliente com o mesmo nome");
				// e.printStackTrace();
			}
		}
	}

	/**
	 * The Class ThreadSendToClient. Send out messages to the client from the
	 * server if the message is an error disconnects the client
	 */
	private class ThreadSendToClient extends Thread {
		@Override
		public void run() {
			try {
				while (!isInterrupted()) {
					ClientSideMessageImp messageOut = getMessagesFromServerToClient()
							.take();
					out.writeObject(messageOut);

					if (messageOut.getType() == ClientSideMessageImp.Type.ERROR) {
						getServerComm().disconnectClient(nickname);
						disconnectClient();
					}
				}
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				// e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				// e.printStackTrace();
			}
		}
	}

	/**
	 * Starts the handling threads
	 */
	public void start() {
		for (Thread t : getActiveThreads()) {
			t.start();
		}
	}

	/**
	 * Disconnect client, interrupt the client's threads, clears client's
	 * messages from queue and closes the socket
	 */
	public void disconnectClient() {
		//
		getMessagesFromServerToClient().clear();
		for (Thread t : getActiveThreads()) {
			t.interrupt();
		}
		try {
			socket.close();
		} catch (IOException e) {
		}

	}

	

	// Self explanatory getters and setters...
	public BlockingQueue<ClientSideMessageImp> getMessagesFromServerToClient() {
		return messagesFromServerToClient;
	}

	public LinkedList<Thread> getActiveThreads() {
		return activeThreads;
	}

	public ServerCommImp getServerComm() {
		return serverComm;
	}

	public ClientHandler getHandler() {
		return handler;
	}

	public String getNickname() {
		return nickname;
	}

	public void setNickname(String nickname) {
		this.nickname = nickname;
	}

}
